// bind 方法可以绑定 this 指向
// bind 方法返回一个绑定后的函数
// 如果绑定的函数被 new，当前函数的 this 就是当前的实例
// new 出来的实例要保证原函数的原型对象上的属性不能丢失
Function.prototype.bind = function (thisArg) {
    let that = this
    let bindArgs = Array.prototype.splice.call(arguments, 1)

    function Fn () {}

    function FnBound () {
        let args = Array.prototype.slice.call(arguments)
        // this instanceof fnBound为true表示构造函数的情况。如new bindFn(9)
        let _this = this instanceof FnBound ? this : thisArg
        return that.apply(_this, bindArgs.concat(args))
    }

    // 维护原型关系
    Fn.prototype = this.prototype
    FnBound.prototype = new Fn()
    return FnBound
}

// Test
// 用法一:
console.log('用法一')
let person1 = {
    name: 'Cherry',
}

function fn (name, age) {
    console.log(this.name + '养了1只' + name + '今年' + age + '岁')
}

let bindFn1 = fn.bind(person1, '猫')

bindFn1(2) // Cherry养了一只猫今年2了

// 用法二：
console.log('用法二')
let person2 = {
    name: 'Cherry',
}

function fn2 (name, age) {
    this.say = '说话'
    console.log(this)  // fn2 { say: '说话' }
    console.log(this.name + '养了2只' + name + '今年' + age + '岁', this.say) // undefined养了一只猫今年9岁了 说话
}

let bindFn2 = fn2.bind(person2, '猫')
let instance2 = new bindFn2(9)

// 用法三：
console.log('用法三')
let person3 = {
    name: 'Cherry',
}

function fn3 (name, age) {
    this.say = '说话'
    console.log(this.name + '养了3只' + name + '今年' + age + '岁', this.say)
}

fn3.prototype.flag = '哺乳类'
let bindFn3 = fn3.bind(person3, '猫')
let instance3 = new bindFn3(9)
console.log(instance3.flag) // 哺乳类
